<?php

/*
 * Copyright (C) 2009 - 2011 Pham Cong Dinh
 *
 * This file is part of Spica.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

/**
 * Spica macros specifies a set of rules to declare instructions that should be mapped to
 * an output sequence (also often a sequence of characters) according to a defined procedure.
 *
 * @category   spica
 * @package    core
 * @package    filter
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      February 22, 2010
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: MacroFilter.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaMacroFilter
{
    /**
     * The tag start.
     *
     * @var string
     */
    private $_tagStart = "<s:macro>";
    /**
     * The tag finish.
     *
     * @var string
     */
    private $_tagEnd = "</s:macro>";
    /**
     * Constructs an object of <code>SpicaMacroFilter</code>.
     */
    public function __construct()
    {

    }

    /**
     * Parses text string to evaluate embedded macros and return processed string.
     *
     * @throws InvalidArgumentException
     * @param string $text Original content
     * @param bool $clean Clean macro code or not
     */
    public function doFilter($text, $clean = false)
    {
        $macros = array();
        $offset = 0;
        $slen = strlen($this->_tagStart);
        $elen = strlen($this->_tagEnd);

        while (true)
        {
            $macro = array();
            $p1 = stripos($text, $this->_tagStart, $offset);

            if (false === $p1)
            {
                break;
            }

            $p2 = stripos($text, $this->_tagEnd, $p1 + $slen);

            if (false === $p2)
            {
                throw new InvalidArgumentException('The tag ' . $this->_tagStart . ' found at the offset ' . $p1 . ' is not closed properly.');
            }

            $macro['s'] = $p1; // start offset
            $macro['e'] = $p2; // end offset
            $macros[] = $macro;
            $offset = $offset + $p2 + $elen;
        }

        $out = '';
        $offset = 0;
        $dom = new DOMDocument('1.0', 'utf-8');

        for ($i = 0, $len = count($macros); $i < $len; $i++)
        {
            $out .= substr($text, $offset, $macros[$i]['s']);
            $xml = substr($text, $macros[$i]['s'], $macros[$i]['e'] - $macros[$i]['s'] + $elen);
            $xml = '<?xml version="1.0" encoding="utf-8"?><spicamacros xmlns:s="http://www.phpvietnam.net/spica/2010">' . $xml . '</spicamacros>';
            $success = $dom->loadXML($xml);

            if (false === $success)
            {
                throw new InvalidArgumentException('The macro ' . $this->_tagStart . ' found from the offset ' . $macros[$i]['s'] . ' to ' . $macros[$i]['s'] . ' is not declared properly.');
            }

            $out .= $this->evaluate($dom);
            $offset = $macros[$i]['e'] + $elen;
        }

        if ($offset < strlen($text))
        {
            $out .= substr($text, $offset);
        }

        return $out;
    }

    /**
     * Evaluates macro declaration and returns its content.
     *
     * @param DOMDocument $doc Macro declaration in DOMDocument format
     * @return string|null
     */
    public function evaluate($doc)
    {
        // DOMNodeList -> DOMElement
        $macro = $doc->getElementsByTagNameNS('http://www.phpvietnam.net/spica/2010', 'macro')->item(0);
        $type = 'print';
        $class = null;
        $params = array();

        /* @var $node DOMNode */
        foreach ($macro->childNodes as $node)
        {
            switch ($node->localName)
            {
                case 'type':
                    if ('daemon' === $node->nodeValue)
                    {
                        $type = 'daemon';
                    }

                    break;

                case 'class':
                    $class = $node->nodeValue;

                    if (empty($class))
                    {
                        throw new InvalidArgumentException('The element "class" must be not empty.');
                    }

                    break;

                default: // NULL
                    break;
            }
        }

        if (false === class_exists($class, false))
        {
            include_once 'filter/'.$class.'.php';
        }

        $o = new $class();

        if (true === method_exists($o, 'run'))
        {
            if ('print' === $type)
            {
                return $o->run();
            }

            $o->run();
        }

        return null;
    }

    /**
     * Checks if the macro declaration is valid.
     *
     * @throws InvalidArgumentException when $text is not valid
     * @param  string $text
     * @return bool Returns <code>true</code> if the macro declaration is valid
     */
    public function validate($text)
    {
        $offset = 0;
        $macros = array();
        $slen = strlen($this->_tagStart);
        $elen = strlen($this->_tagEnd);

        while (true)
        {
            $macro = array();
            $p1 = stripos($text, $this->_tagStart, $offset);

            if (false === $p1)
            {
                break;
            }

            $p2 = stripos($text, $this->_tagEnd, $p1 + $slen);

            // No end tag
            if (false === $p2)
            {
                throw new InvalidArgumentException('The tag ' . $this->_tagStart . ' found at the offset ' . $p1 . ' is not closed properly.');
            }

            $macro['s'] = $p1; // start offset
            $macro['e'] = $p2; // end offset
            $macros[] = $macro;
            $offset = $offset + $p2 + $elen;
        }

        $dom = new DOMDocument('1.0', 'utf-8');

        for ($i = 0, $len = count($macros); $i < $len; $i++)
        {
            $xml = substr($text, $macros[$i]['s'], $macros[$i]['e'] - $macros[$i]['s'] + $elen);
            $xml = '<?xml version="1.0" encoding="utf-8"?><spicamacros xmlns:s="http://www.phpvietnam.net/spica/2010">' . $xml . '</spicamacros>';
            $success = $dom->loadXML($xml);

            if (false === $success)
            {
                throw new InvalidArgumentException('The macro ' . $this->_tagStart . ' found from the offset ' . $macros[$i]['s'] . ' to ' . $macros[$i]['s'] . ' is not declared properly.');
            }
        }

        return true;
    }
}

?>